Flutter Maps
We need to improve the functionality of our app. Currently we have a list of just two questions and no answer is associated with the question.
var questions = [
'What\'s your favorite color?',
'What\'s your favorite animal?'
];
The question should also have the information about available answers. We need more complex object which can group multiple pieces of information together.
We could create a new class which has all these features we need and use that class to create objects and that is perfectly fine. However, we’ll use a different data structure that’s built in to Dart and that is a map.
map is a collection of key value pairs. Lets create a map where we have three questions and four answers for each question as below,
var questions = [
{
'questionText': 'What\'s your favorite color?',
'answers': [ 'Black', 'Red', 'Green', 'White'],
},
{
'questionText': 'What\'s your favorite animal?',
'answers': [ 'Leopard', 'Markhor', 'Deer', 'Rabbit'],
},
{
'questionText': 'What\'s your favorite lake?',
'answers': [ 'Saif-ul-Malook', 'Doodi pat sar', 'Katora', 'Saral'],
},
];
Here is our main.dart file where we are going to map lists to widgets.
//File Name: main.dart
import 'package:flutter/material.dart';
/* ./ means look in the main folder where main.dart resides and then the
* name of the file which you wana import
*/
import './question.dart';
import './answer.dart';
/*
* The arrow function allows us to create a simplified function consisting of a single expression.
* We can omit the curly brackets and the return keyword
*/
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
/*
* createState() is a method that takes no arguments but returns a State object
* which is connected to a StatefulWidget
*/
@override
State<StatefulWidget> createState() {
return _MyAppState();
} //createState()
} //StatefulWidget
/* A leading underscore makes the property private, here leading underscore will turn
* this public class in to private class. Now MyAppState class can only be used in
* main.dart file, in our case in MyApp class.
*/
class _MyAppState extends State<MyApp> {
var _questionIndex = 0;
void _answerQuestion() {
setState(() {
_questionIndex++;
});
print(_questionIndex);
}
@override
Widget build(BuildContext context) {
var questions = [
{
'questionText': 'What\'s your favorite color?',
'answers': ['Black', 'Red', 'Green', 'White'],
},
{
'questionText': 'What\'s your favorite animal?',
'answers': ['Leopard', 'Markhor', 'Deer', 'Rabbit'],
},
{
'questionText': 'What\'s your favorite lake?',
'answers': ['Saif-ul-Malook', 'Doodi pat sar', 'Katora', 'Saral'],
},
];
return MaterialApp(
/*
* A Scaffold Widget provides a framework which implements the basic material design visual
* layout structure of the flutter app.
*/
home: Scaffold(
appBar: AppBar(
title: Text('First Flutter App'),
),
body: Column(
children: <Widget>[
Question(
questions[_questionIndex]['questionText'],
),
/*
* for answers, transform list of maps in to list of widgets
* map() method executes a function (anonymous) which we have to pass as an argument.
* it returns a widget
* ... is spread operator, it takes a list and pulls all the values in the list out of it
* and add them to surrounding list as individual value. Means, we don't add a list to a list
* but values of the list to a list.
*/
...(questions[_questionIndex]['answers'] as List<String>)
.map((answer) {
return Answer(_answerQuestion, answer);
}).toList(),
],
),
),
);
} //build()
} //MyApp()
Here is answer.dart file
//FileName: answer.dart
import 'package:flutter/material.dart';
class Answer extends StatelessWidget {
final Function selectHandler;
final String answerText;
Answer(this.selectHandler, this.answerText);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity, ////double.infinity means occupy the whole screen (width in this case)
margin: EdgeInsets.all(10), //apply margin of 10 pts to all 4 sides
/*
* We have provided 3 named arguments here to ElevatedButton()
* 1. child: which is a Text() widget
* 2. onPressed: which is a function we get via constructor
* 3. style: where we change the color, padding and text style
*/
child: ElevatedButton(
child: Text(answerText),
//onPressed takes a function
onPressed: selectHandler,
style: ElevatedButton.styleFrom(
primary: Colors.blue, //the button's background color
onPrimary: Colors.white, //the button's text color
padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20),
textStyle: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
), //.styleFrom
), //ElevatedButton
); //Container
}
}
The question widget will remain unchanged.
//FileName: question.dart
import 'package:flutter/material.dart';
class Question extends StatelessWidget {
/*
* final keywords tells dart that this value will never change after its
* initialization here int he constructor
*/
final String questionText;
/* Lets have a constructor to initialize this widget class questionText
* Now the first positional argument passed to this constructor will be stored in
* questionText variable
*/
Question(this.questionText);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity, //double.infinity means occupy the whole screen (width in this case)
margin: EdgeInsets.all(10), //apply margin of 10 pts to all 4 sides
child: Text(
questionText,
style: TextStyle(fontSize: 26),
textAlign: TextAlign.center,
), //Text
); //Container()
} //build
} //Question
Here is the output
Happy Coding!